################################################################################
#
#Copyright (c) 2011, Petr Skramovsky
#All rights reserved.
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions are met:
#    * Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in the
#      documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
#ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
#WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
#DISCLAIMED. IN NO EVENT SHALL Petr Skramovsky BE LIABLE FOR ANY
#DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
#(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
#LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
################################################################################

import c4d

TYPE_POLYGON = 5100
TYPE_LIGHT = 5102
TYPE_NURBS = 5120

TEX_CAULK = "common/caulk"
TEX_COORDS = " 0 0 0 1 1 0 0 0\n"
VEC_FMT_FLOAT = "( %f %f %f ) "
VEC_FMT_INT = "( %d %d %d ) "
VALID_PATCH_DIMS = 3,5,7,9,11,13,15

EXP_PARAM_POLY = "poly"
EXP_PARAM_LIGHT = "light"
EXP_PARAM_NURBS = "nurbs"
EXP_PARAM_COORD = "coord"
EXP_PARAM_NORMAL = "normal"
EXP_PARAM_CAULK = "caulk"
EXP_PARAM_TEX = "tex"
EXP_PARAM_SCALE = "scale"
EXP_PARAM_EXT = "ext"
EXP_PARAM_GRID = "grid"

def polynormal(p1, p2, p3):
  u = p2 - p1
  v = p3 - p1
  return u.Cross(v).GetNormalized()

def pointnormals(polys, points):
  ip = 0
  normals = []
  pps = {}
  for poly in polys:
    for p in poly.a, poly.b, poly.c, poly.d:
      if p not in pps:
        pps[p] = []
      pps[p].append(ip)
    normals.append(polynormal(points[poly.a], points[poly.b], points[poly.c]))
    ip += 1
  ppns = {}
  for pp in pps:
    rn = c4d.Vector(0, 0, 0)
    for n in pps[pp]:
      rn += normals[n]
    c = len(pps[pp])
    rn /= c
    ppns[pp] = rn.GetNormalized()
  return ppns

def snap2grid(vec, grid):
  return c4d.Vector(round(vec.x/grid)*grid, round(vec.y/grid)*grid, round(vec.z/grid)*grid)

def snap_points(points,grid):
  rp = []
  for p in points:
    rp.append(snap2grid(p,grid))
  return rp

def scale_points(points, scale):
  rp = []
  for p in points:
    rp.append(p * scale)
  return rp

def convert_points(points):
  for p in points:
    t = p.y
    p.y = -p.z
    p.z = t

def trans_points(points,mat):
  rp = []
  for p in points:
    rp.append( p * mat )
  return rp

def light2entity(file, obj, params):
  scale = params[EXP_PARAM_SCALE]
  qcoord = params[EXP_PARAM_COORD]
  color = obj[c4d.LIGHT_COLOR]
  light = obj[c4d.LIGHT_BRIGHTNESS] * scale * 100
  pos = obj.GetAbsPos() * scale
  if qcoord:
    convert_points([pos])

  file.write('{\n')
  file.write('"classname" "light"\n')
  file.write('"light" "%.6f"\n' % (light))
  file.write('"origin" "%.6f %.6f %.6f"\n' % (pos.x, pos.y, pos.z))
  file.write('"_color" "%.6f %.6f %.6f"\n' % (color.x, color.y, color.z))
  file.write('}\n')

def poly2brush(file, obj, params):
  scale = params[EXP_PARAM_SCALE]
  ext = params[EXP_PARAM_EXT]
  grid = params[EXP_PARAM_GRID]
  qcoord = params[EXP_PARAM_COORD]
  pervert = params[EXP_PARAM_NORMAL]
  caulk = params[EXP_PARAM_CAULK]
  tex = params[EXP_PARAM_TEX]


  polys = obj.GetAllPolygons()
  points = obj.GetAllPoints()
  name = obj.GetName()
  ext *= scale

  vec_fmt = VEC_FMT_FLOAT
  points = trans_points(points,obj.GetMg())
  if grid > 0:
    vec_fmt = VEC_FMT_INT
  if qcoord:
    convert_points(points)
  points = scale_points(points, scale)
  if grid > 0:
    points = snap_points(points,grid)
  if pervert:
    normals = pointnormals(polys, points)
  else:
    normals = []
    for poly in polys:
      normals.append(polynormal(points[poly.a], points[poly.b], points[poly.c]))

  file.write('// entity %s\n{\n"classname" "func_group"\n' % name)
  ip = 0
  for poly in polys:
    extps = []
    ps = []
    for i in poly.a, poly.b, poly.c, poly.d:
      if pervert:
        n = normals[i]
      else:
        n = normals[ip]
      extps.append(points[i] - (n * ext))
      ps.append(points[i])
    if grid > 0:
      extps = snap_points(extps,grid)

    file.write("// polygon %d\n{\n" % (ip))
    for p in ps[2 :: -1 ]:
      file.write(vec_fmt % (p.x, p.y, p.z))
    file.write(tex)
    file.write(TEX_COORDS)

    for p in extps[ : 3 ]:
      file.write(vec_fmt % (p.x, p.y, p.z))
    if caulk:
      file.write(TEX_CAULK)
    else:
      file.write(tex)
    file.write(TEX_COORDS)

    if ps[2] == ps[3]:
      index_pairs = ((0, 1), (1, 2), (2, 0))
    else:
      index_pairs = ((0, 1), (1, 2), (2, 3), (3, 0))
    for i1, i2 in index_pairs:
      for p in ps[i1], ps[i2], extps[i2]:
        file.write(vec_fmt % (p.x, p.y, p.z))
      if caulk:
        file.write(TEX_CAULK)
      else:
        file.write(tex)
      file.write(TEX_COORDS)
    file.write("}\n")
    ip += 1
  file.write("}\n")

def bezier2patch(file, obj, params):
  u = obj[c4d.BEZIEROBJECT_SUBX]
  v = obj[c4d.BEZIEROBJECT_SUBY]
  cu = obj[c4d.BEZIEROBJECT_CLOSEX]
  cv = obj[c4d.BEZIEROBJECT_CLOSEY]
  if not u in VALID_PATCH_DIMS or not v in VALID_PATCH_DIMS or cu or cv:
    return

  scale = params[EXP_PARAM_SCALE]
  grid = params[EXP_PARAM_GRID]
  qcoord = params[EXP_PARAM_COORD]
  tex = params[EXP_PARAM_TEX]

  points = obj.GetAllPoints()
  name = obj.GetName()

  fmt = ' ( %.6f %.6f %.6f 0 0 )'
  if grid > 0:
    fmt = ' ( %d %d %d 0 0 )'
  points = trans_points(points,obj.GetMg())
  if qcoord:
    convert_points(points)
  points = scale_points(points, scale)
  if grid > 0:
    points = snap_points(points,grid)

  file.write('// entity %s\n{\n"classname" "func_group"\n' % name)
  file.write('{\n')
  file.write('patchDef2\n')
  file.write('{\n')
  file.write('%s\n' % tex)
  file.write('( %d %d 0 0 0 )\n' % (u, v) )
  file.write('(\n')
  ui = 0
  for p in points:
    if ui == 0:
		  file.write('(')
    ui += 1
    file.write(fmt % ( p.x, p.y, p.z) )
    if ui == u:
      file.write(' )\n')
      ui = 0
  file.write(')\n')
  file.write('}\n')
  file.write('}\n')
  file.write('}\n')


def write_object(file, obj, params):
  t = obj.GetType()
  if t == TYPE_POLYGON and params[EXP_PARAM_POLY]:
    poly2brush(file, obj, params)
  elif t == TYPE_NURBS and params[EXP_PARAM_NURBS]:
    bezier2patch(file,obj,params)
  elif t == TYPE_LIGHT and params[EXP_PARAM_LIGHT]:
    light2entity(file, obj, params)
  else:
    print "WARNING : unsupported object type %s" % (obj.GetName())

def list_objects(objs, res):
  for obj in objs:
    res.append(obj)
    list_objects(obj.GetChildren(), res)

def export(doc, path, params):
  file = open(path, "w")
  file.write('// entity 0\n{\n"classname" "worldspawn"\n}\n')
  objs = []
  list_objects(doc.GetObjects(), objs)
  for obj in objs:
    write_object(file, obj, params)
  file.close()

if __name__ == '__main__':
  params = {}
  params[EXP_PARAM_SCALE] = 3
  params[EXP_PARAM_EXT] = 32
  params[EXP_PARAM_GRID] = 1
  params[EXP_PARAM_COORD] = True
  params[EXP_PARAM_NORMAL] = True
  params[EXP_PARAM_CAULK] = True
  params[EXP_PARAM_TEX] = "radiant/notex"
  params[EXP_PARAM_POLY] = True
  params[EXP_PARAM_LIGHT] = True
  params[EXP_PARAM_NURBS] = True

  export(doc, "C:\\Program Files (x86)\\Quake III Arena\\baseq3\\maps\\testexp.map", params)
  
